/**
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.gitee.huangjianbo.supos.openapi.core;

import java.io.*;
import java.util.Locale;

import io.gitee.huangjianbo.supos.openapi.core.config.SuposApiClientConfigurer;
import io.gitee.huangjianbo.supos.openapi.core.config.SuposEndpoint;
import io.gitee.huangjianbo.supos.openapi.core.entity.SuposVersion;
import io.gitee.huangjianbo.supos.openapi.core.errors.ApiClientException;
import io.gitee.huangjianbo.supos.openapi.core.http.*;
import io.gitee.huangjianbo.supos.openapi.core.http.request.EmptyArgs;
import io.gitee.huangjianbo.supos.openapi.core.http.request.RequestArguments;
import io.gitee.huangjianbo.supos.openapi.core.http.request.types.RFile;
import io.gitee.huangjianbo.supos.openapi.core.http.request.types.RList;
import io.gitee.huangjianbo.supos.openapi.core.http.request.types.RMap;
import io.gitee.huangjianbo.supos.openapi.core.http.request.types.RSet;
import io.gitee.huangjianbo.supos.openapi.core.http.response.ListResult;
import io.gitee.huangjianbo.supos.openapi.core.http.response.PageResult;
import io.gitee.huangjianbo.supos.openapi.core.http.response.Result;
import io.gitee.huangjianbo.supos.openapi.core.thread.ClientContextHolder;

/**
 * 请求封装接口
 *
 * @author tomcat
 * @version 1.0
 */
public interface SuposApiClient extends SuposApiClientConfigurer {

    /**
     * 设置supOS的访问令牌
     *
     * @param ticket 访问令牌
     * @return 返回接口实例
     */
    default SuposApiClient ticket(String ticket) {
        ClientContextHolder.getContext().setTicket(ticket);
        return this;
    }

    /**
     * 设置AccessKey
     *
     * @param accessKey AK
     * @return 返回接口实例
     */
    default SuposApiClient accessKey(String accessKey) {
        ClientContextHolder.getContext().setAccessKey(accessKey);
        return this;
    }

    /**
     * 设置SecretKey
     *
     * @param secretKey SK
     * @return 返回接口实例
     */
    default SuposApiClient secretKey(String secretKey) {
        ClientContextHolder.getContext().setSecretKey(secretKey);
        return this;
    }

    /**
     * 设置supOS访问地址
     *
     * @param endpoint 访问地址，如：http[s]://ip:port, {@link SuposEndpoint}
     * @return 返回接口实例
     */
    default SuposApiClient endpoint(SuposEndpoint endpoint) {
        ClientContextHolder.getContext().setEndpoint(endpoint);
        return this;
    }

    /**
     * 设置租户ID
     *
     * @param tenantId 租户ID
     * @return 返回接口实例
     */
    default SuposApiClient tenantId(String tenantId) {
        ClientContextHolder.getContext().setTenantId(tenantId);
        return this;
    }

    /**
     * 设置请求语言
     *
     * @param locale 语言，{@link Locale}
     * @return 返回接口实例
     */
    default SuposApiClient language(Locale locale) {
        return language(HttpLanguage.of(locale));
    }

    /**
     * 设置请求语言
     *
     * @param language 语言，{@link HttpLanguage}
     * @return 返回接口实例
     */
    default SuposApiClient language(HttpLanguage language) {
        ClientContextHolder.getContext().setLanguage(language);
        return this;
    }

    /**
     * 请求带返回参
     *
     * @param path   请求路径, {@link HttpPath}
     * @param args   请求参数
     * @param header 请求头, {@link HttpHeader}
     * @param clazz  返回参类型
     * @param <R>    返回参类型
     * @param <T>    请求参类型，实现接口：{@link RequestArguments}
     * @return 返回参
     */
    <R, T extends RequestArguments> R callWithResponse(HttpPath path, T args, HttpHeader header, Class<R> clazz);

    /**
     * 请求带返回参
     *
     * @param path   请求路径, {@link HttpPath}
     * @param args   请求参数
     * @param header 请求头, {@link HttpHeader}
     * @param clazz  返回参类型
     * @param <R>    返回参类型
     * @param <T>    请求参类型，实现接口：{@link RequestArguments}
     * @return 返回参：{@link Result}
     */
    <R, T extends RequestArguments> Result<R> callWithResponseForResult(HttpPath path, T args, HttpHeader header, Class<R> clazz);

    /**
     * 请求带返回参
     *
     * @param path   请求路径, {@link HttpPath}
     * @param args   请求参数
     * @param header 请求头, {@link HttpHeader}
     * @param clazz  返回参类型
     * @param <R>    返回参类型
     * @param <T>    请求参类型，实现接口：{@link RequestArguments}
     * @return 返回参：{@link ListResult}
     */
    <R, T extends RequestArguments> ListResult<R> callWithResponseForListResult(HttpPath path, T args, HttpHeader header, Class<R> clazz);

    /**
     * 请求带返回参
     *
     * @param path   请求路径, {@link HttpPath}
     * @param args   请求参数
     * @param header 请求头, {@link HttpHeader}
     * @param clazz  返回参类型
     * @param <R>    返回参类型
     * @param <T>    请求参类型，实现接口：{@link RequestArguments}
     * @return 返回参：{@link PageResult}
     */
    <R, T extends RequestArguments> PageResult<R> callWithResponseForPageResult(HttpPath path, T args, HttpHeader header, Class<R> clazz);

    /**
     * 请求带返回参（无请求参）
     *
     * @param path   请求路径, {@link HttpPath}
     * @param header 请求头, {@link HttpHeader}
     * @param clazz  返回类型
     * @param <R>    返回参类型
     * @return 返回参
     */
    default <R> R callWithResponseNoArgs(HttpPath path, HttpHeader header, Class<R> clazz) {
        return callWithResponse(path, EmptyArgs.getDefault(), header, clazz);
    }

    /**
     * 请求带返回参
     *
     * @param path   请求路径, {@link HttpPath}
     * @param args   请求参数, 以{@link RList}进行包装
     * @param header 请求头, {@link HttpHeader}
     * @param clazz  返回参类型
     * @param <R>    返回参类型
     * @param <T>    请求参类型, 实现接口：{@link RequestArguments}
     * @return 返回参
     */
    default <R, T extends RequestArguments> R callWithResponseByList(HttpPath path, RList<T> args, HttpHeader header, Class<R> clazz) {
        return callWithResponse(path, args, header, clazz);
    }

    /**
     * 请求带返回参
     *
     * @param path   请求路径, {@link HttpPath}
     * @param args   请求参数, 以{@link RSet}进行包装
     * @param header 请求头, {@link HttpHeader}
     * @param clazz  返回类型
     * @param <R>    返回参类型
     * @param <T>    请求参类型, 实现接口：{@link RequestArguments}
     * @return 返回参
     */
    default <R, T extends RequestArguments> R callWithResponseBySet(HttpPath path, RSet<T> args, HttpHeader header, Class<R> clazz) {
        return callWithResponse(path, args, header, clazz);
    }

    /**
     * 请求带返回参
     *
     * @param path   请求路径, {@link HttpPath}
     * @param args   请求参数, 以{@link RMap}进行包装
     * @param header 请求头, {@link HttpHeader}
     * @param clazz  返回类型
     * @param <R>    返回参类型
     * @param <K>    Map key类型
     * @param <V>    Map value类型
     * @return 返回参
     */
    default <R, K, V> R callWithResponseByMap(HttpPath path, RMap<K, V> args, HttpHeader header, Class<R> clazz) {
        return callWithResponse(path, args, header, clazz);
    }

    /**
     * 请求不带返回参
     *
     * @param path   请求路径, {@link HttpPath}
     * @param args   请求参数
     * @param header 请求头, {@link HttpHeader}
     * @param <T>    请求参类型, 实现接口：{@link RequestArguments}
     */
    <T extends RequestArguments> void callWithoutResponse(HttpPath path, T args, HttpHeader header);

    /**
     * 请求不带返回参
     *
     * @param path   请求路径, {@link HttpPath}
     * @param header 请求头, {@link HttpHeader}
     */
    default void callWithoutResponseNoArgs(HttpPath path, HttpHeader header) {
        callWithoutResponse(path, EmptyArgs.getDefault(), header);
    }

    /**
     * 请求不带返回参
     *
     * @param path   请求路径, {@link HttpPath}
     * @param args   请求参数, 以{@link RList}进行包装
     * @param header 请求头, {@link HttpHeader}
     * @param <T>    请求参类型, 实现接口：{@link RequestArguments}
     */
    default <T extends RequestArguments> void callWithoutResponseByList(HttpPath path, RList<T> args, HttpHeader header) {
        callWithoutResponse(path, args, header);
    }

    /**
     * 请求不带返回参
     *
     * @param path   请求路径, {@link HttpPath}
     * @param args   请求参数, 以{@link RSet}进行包装
     * @param header 请求头, {@link HttpHeader}
     * @param <T>    请求参类型, 实现接口：{@link RequestArguments}
     */
    default <T extends RequestArguments> void callWithoutResponseBySet(HttpPath path, RSet<T> args, HttpHeader header) {
        callWithoutResponse(path, args, header);
    }

    /**
     * 请求不带返回参
     *
     * @param path   请求路径, {@link HttpPath}
     * @param args   请求参数, 以{@link RMap}进行包装
     * @param header 请求头, {@link HttpHeader}
     * @param <K>    Map key类型
     * @param <V>    Map value类型
     */
    default <K, V> void callWithoutResponseByMap(HttpPath path, RMap<K, V> args, HttpHeader header) {
        callWithoutResponse(path, args, header);
    }

    /**
     * 上传文件（有返回参）
     *
     * @param path   请求路径, {@link HttpPath}
     * @param file   文件，以{@link RFile}进行包装
     * @param args   请求参数
     * @param header 请求头, {@link HttpHeader}
     * @param clazz  返回参类型
     * @param <R>    返回参类型
     * @param <T>    请求参类型, 实现接口：{@link RequestArguments}
     * @return 返回参
     */
    <R, T extends RequestArguments> R upload(HttpPostPath path, RFile file, T args, HttpHeader header, Class<R> clazz);

    /**
     * 上传文件（有返回参）
     *
     * @param path   请求路径, {@link HttpPath}
     * @param file   文件，{@link File}
     * @param args   请求参数
     * @param header 请求头, {@link HttpHeader}
     * @param clazz  返回参类型
     * @param <R>    返回参类型
     * @param <T>    请求参类型, 实现接口：{@link RequestArguments}
     * @return 返回参
     */
    default <R, T extends RequestArguments> R upload(HttpPostPath path, File file, T args, HttpHeader header, Class<R> clazz) {
        return upload(path, new RFile(file), args, header, clazz);
    }

    /**
     * 上传文件（有返回参）
     *
     * @param path   请求路径, {@link HttpPath}
     * @param file   文件，以{@link RFile}进行包装
     * @param header 请求头, {@link HttpHeader}
     * @param clazz  返回参类型
     * @param <R>    返回参类型
     * @return 返回参
     */
    default <R> R upload(HttpPostPath path, RFile file, HttpHeader header, Class<R> clazz) {
        return upload(path, file, EmptyArgs.getDefault(), header, clazz);
    }

    /**
     * 上传文件（有返回参）
     *
     * @param path   请求路径, {@link HttpPath}
     * @param file   文件，{@link File}
     * @param header 请求头, {@link HttpHeader}
     * @param clazz  返回参类型
     * @param <R>    返回参类型
     * @return 返回参
     */
    default <R> R upload(HttpPostPath path, File file, HttpHeader header, Class<R> clazz) {
        return upload(path, new RFile(file), EmptyArgs.getDefault(), header, clazz);
    }

    /**
     * 上传文件（无返回参）
     *
     * @param path   请求路径, {@link HttpPath}
     * @param file   文件，以{@link RFile}进行包装
     * @param args   请求参数
     * @param header 请求头, {@link HttpHeader}
     * @param <T>    请求参类型, 实现接口：{@link RequestArguments}
     */
    <T extends RequestArguments> void upload(HttpPostPath path, RFile file, T args, HttpHeader header);

    /**
     * 上传文件（无返回参）
     *
     * @param path   请求路径, {@link HttpPath}
     * @param file   文件，{@link File}
     * @param args   请求参数
     * @param header 请求头, {@link HttpHeader}
     * @param <T>    请求参类型, 实现接口：{@link RequestArguments}
     */
    default <T extends RequestArguments> void upload(HttpPostPath path, File file, T args, HttpHeader header) {
        upload(path, new RFile(file), args, header);
    }

    /**
     * 上传文件
     *
     * @param path   请求路径, {@link HttpPath}
     * @param file   文件，以{@link RFile}进行包装
     * @param header 请求头, {@link HttpHeader}
     * @param <T>    请求参类型, 实现接口：{@link RequestArguments}
     */
    default <T extends RequestArguments> void upload(HttpPostPath path, RFile file, HttpHeader header) {
        upload(path, file, EmptyArgs.getDefault(), header);
    }

    /**
     * 上传文件
     *
     * @param path   请求路径, {@link HttpPath}
     * @param file   文件，{@link File}
     * @param header 请求头, {@link HttpHeader}
     * @param <T>    请求参类型, 实现接口：{@link RequestArguments}
     */
    default <T extends RequestArguments> void upload(HttpPostPath path, File file, HttpHeader header) {
        upload(path, new RFile(file), header);
    }

    /**
     * 下载文件
     *
     * @param path       请求路径, {@link HttpPath}
     * @param args       请求参
     * @param outputFile 输出文件, {@link File}
     * @param header     请求头, {@link HttpHeader}
     * @param <T>        请求参类型, 实现接口：{@link RequestArguments}
     */
    default <T extends RequestArguments> void download(HttpGetPath path, T args, File outputFile, HttpHeader header) {
        if (outputFile.exists() && !outputFile.delete()) {
            throw new ApiClientException("Fail to delete exists file:" + outputFile.getPath());
        }
        if (!outputFile.mkdirs()) {
            throw new ApiClientException("Fail to create directories:" + outputFile.getParent());
        }
        try (InputStream is = download(path, args, header); FileOutputStream os = new FileOutputStream(outputFile)) {
            if (is != null) {
                byte[] buffer = new byte[1024];
                int read;
                while ((read = is.read(buffer)) != -1) {
                    os.write(buffer, 0, read);
                }
            }
        } catch (IOException e) {
            throw new ApiClientException("Fail to write file: " + outputFile.getPath(), e);
        }
    }

    /**
     * 下载文件
     *
     * @param path   请求路径, {@link HttpPath}
     * @param args   请求参
     * @param header 请求头, {@link HttpHeader}
     * @param <T>    请求参类型, 实现接口：{@link RequestArguments}
     * @return 文件流, {@link InputStream}
     */
    <T extends RequestArguments> InputStream download(HttpGetPath path, T args, HttpHeader header);

    /**
     * 下载文件
     *
     * @param path       请求路径, {@link HttpPath}
     * @param outputFile 输出文件, {@link File}
     * @param header     请求头, {@link HttpHeader}
     */
    default void download(HttpGetPath path, File outputFile, HttpHeader header) {
        download(path, EmptyArgs.getDefault(), outputFile, header);
    }

    /**
     * 获取supOS的版本信息
     *
     * @return 版本信息，{@link SuposVersion}
     */
    default SuposVersion getSupOSVersions() {
        return callWithResponseNoArgs(
                HttpPath.of("/open-api/supos/config/v2/version"),
                HttpHeader.of(),
                SuposVersion.class);
    }
}
